package com.founder.gateway.component.transport.netty.extension.support;

import com.founder.gateway.component.transport.netty.exception.NettyException;
import com.founder.gateway.component.transport.netty.filter.NettyFilterChain;
import com.founder.gateway.component.transport.netty.http.NettyHttpRequest;
import com.founder.gateway.component.transport.netty.http.NettyHttpResponse;
import com.founder.gateway.oauth.service.OAuthService;
import com.founder.gateway.orm.fzapi.bean.entity.AppInfoEntity;
import com.founder.gateway.system.bean.SetuSession;
import com.founder.gateway.system.consts.ApiSetuErrorCode;
import com.founder.gateway.system.consts.Consts;
import com.founder.gateway.system.util.HttpNormUtil;
import org.apache.commons.lang3.StringUtils;
import org.setu.framework.session.SessionContext;
import org.setu.framework.session.thread.SessionThreadLocalHandler;
import org.setu.framework.util.SetuApplicationUtil;
import org.setu.framework.utils.encrypt.Base64Util;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class AuthenticationNettyHttpFilter extends AbstractNettyHttpFilter {
    @Override
    public void doFilter(NettyHttpRequest request, NettyHttpResponse response, NettyFilterChain chain) throws IOException, NettyException {
        String appId;
        String apiSchemeId = chain.getApiSchemeEntity().getId();
        OAuthService oAuthService = SetuApplicationUtil.getBean(OAuthService.class);
        if (Consts.AUTHENTICATION_MODE_BEARER.equalsIgnoreCase(chain.getApiEntityEntity().getAuthentication())) {
            String tokenValue = extractAuthentication(request, Consts.AUTHENTICATION_MODE_BEARER);
            if (tokenValue == null) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_TOKEN_INVALID);
            }
            TokenStore tokenStore = SetuApplicationUtil.getBean(TokenStore.class);
            OAuth2AccessToken token;
            try {
                token = tokenStore.readAccessToken(tokenValue);
                appId = (String) token.getAdditionalInformation().get(Consts.TOKEN_AID);
            } catch (Exception e) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_TOKEN_INVALID);
            }
            if (token.isExpired() || appId == null) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_TOKEN_INVALID);
            }
            if (oAuthService.checkAuthentication(appId, apiSchemeId)) {
                Map<String, String> map = request.getParameterMap().getMapsSingle();
                String userid = request.headers().get(Consts.API_USERID);
                String orgcode = request.headers().get(Consts.API_ORGCODE);
                if (StringUtils.isBlank(userid)) {
                    userid = map.get(Consts.API_USERID);
                }
                if (StringUtils.isBlank(orgcode)) {
                    orgcode = map.get(Consts.API_ORGCODE);
                }
                if (StringUtils.isBlank(userid)) {
                    throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_USER_INVALID);
                }
                SetuSession session = oAuthService.getSessionBean(userid, orgcode);
                if (session == null) {
                    throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_USER_INVALID);
                }
                setSession(session, request, tokenValue, appId, apiSchemeId);
                chain.doFilter(request, response);
            } else {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_UNAUTHORIZED);
            }
        } else if (Consts.AUTHENTICATION_MODE_BASIC.equalsIgnoreCase(chain.getApiEntityEntity().getAuthentication())) {
            String basicValue = extractAuthentication(request, Consts.AUTHENTICATION_MODE_BASIC);
            String appKey, appSecrect;
            if (basicValue == null) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_BASIC_INVALID);
            }
            try {
                String[] info = Base64Util.decode(basicValue).split(":", 2);
                appKey = info[0];
                appSecrect = info[1];
            } catch (Exception e) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_BASIC_INVALID);
            }
            AppInfoEntity appInfoEntity = oAuthService.getClientByAppKey(appKey);
            if (appInfoEntity == null) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_UNAUTHORIZED);
            }
            if (!appSecrect.equals(appInfoEntity.getAppSecrect())) {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_BASIC_INVALID);
            }
            appId = appInfoEntity.getId();
            if (oAuthService.checkAuthentication(appId, apiSchemeId)) {
                setSession(new SetuSession(), request, basicValue, appId, apiSchemeId);
                chain.doFilter(request, response);
            } else {
                throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_UNAUTHORIZED);
            }
        } else {
            throw new NettyException(ApiSetuErrorCode.API_AUTHENTICATION_MODE_UNKNOWN);
        }
    }

    private static void setSession(SetuSession session, NettyHttpRequest request, String token, String appId, String apiSchemeId) {
        session.sessionContenxt = new SessionContext();
        session.sessionContenxt.tokenId = token;
        session.sessionContenxt.remoteIp = request.getRemoteAddr();
        session.sessionContenxt.requestUrl = request.getAction();
        session.sessionContenxt.callSource = appId;
        session.initRequestKey();
        session.setApiSchemeId(apiSchemeId);
        session.setAppId(appId);
        session.setRemoteIp(request.getRemoteAddr());
        SessionThreadLocalHandler.localSession.set(session);
    }

    private static String extractAuthentication(NettyHttpRequest request, String type) {
        List<String> values = request.headers().getAll(HttpNormUtil.HEADER_FOUNDER + Consts.AUTHORIZATION_HEADER);
        for (String value : values) {
            if (value.toLowerCase().startsWith(type.toLowerCase())) {
                String authHeaderValue = value.substring(type.length()).trim();
                int commaIndex = authHeaderValue.indexOf(',');
                if (commaIndex > 0) {
                    authHeaderValue = authHeaderValue.substring(0, commaIndex);
                }
                return authHeaderValue;
            }
        }
        return null;
    }
}
